﻿#region Copyright and License
//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;

using log4net.Layout;
using log4net.Core;
using log4net.Util;

namespace log4net.Appender
{
    public class TCPAppender : AppenderSkeleton
    {
        private const int _minTimeBeforeNextAttemptInSec = 30;

        private static DateTime? _nextTime;

        // do not remove
        public TCPAppender()
        {
        }

        public IPAddress RemoteAddress
        {
            get { return m_remoteAddress; }
            set { m_remoteAddress = value; }
        }

        public int RemotePort
        {
            get { return m_remotePort; }
            set
            {
                if (value < IPEndPoint.MinPort || value > IPEndPoint.MaxPort)
                {
                    throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)value,
                        "The value specified is less than " +
                        IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) +
                        " or greater than " +
                        IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
                }
                else
                {
                    m_remotePort = value;
                }
            }
        }

        public int TimeBeforeNextAttemptInSec
        {
            get { return _time_before_next_attempt; }
            set
            {
                if (value < _minTimeBeforeNextAttemptInSec)
                {
                    //_time_before_next_attempt = 30;
                    // TODO: write to file
                    throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)value,
                        "The value specified is less than " + _minTimeBeforeNextAttemptInSec.ToString() + " sec");
                }
                else
                {
                    _time_before_next_attempt = value;
                }
            }
        }

        public Encoding Encoding
        {
            get { return m_encoding; }
            set { m_encoding = value; }
        }

        protected TcpClient Client
        {
            get { return this.m_client; }
            set { this.m_client = value; }
        }

        /// <summary>
        /// Gets or sets the cached remote endpoint to which the logging events should be sent.
        /// </summary>
        /// <value>
        /// The cached remote endpoint to which the logging events will be sent.
        /// </value>
        /// <remarks>
        /// The <see cref="ActivateOptions" /> method will initialize the remote endpoint
        /// with the values of the <see cref="RemoteAddress" /> and <see cref="RemotePort"/>
        /// properties.
        /// </remarks>
        protected IPEndPoint RemoteEndPoint
        {
            get { return this.m_remoteEndPoint; }
            set { this.m_remoteEndPoint = value; }
        }

        /// <summary>
        /// Initialize the appender based on the options set.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This is part of the <see cref="IOptionHandler"/> delayed object
        /// activation scheme. The <see cref="ActivateOptions"/> method must
        /// be called on this object after the configuration properties have
        /// been set. Until <see cref="ActivateOptions"/> is called this
        /// object is in an undefined state and must not be used.
        /// </para>
        /// <para>
        /// If any of the configuration properties are modified then
        /// <see cref="ActivateOptions"/> must be called again.
        /// </para>
        /// <para>
        /// The appender will be ignored if no <see cref="RemoteAddress" /> was specified or
        /// an invalid remote or local TCP port number was specified.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">The required property <see cref="RemoteAddress" /> was not specified.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The TCP port number assigned to <see cref="LocalPort" /> or <see cref="RemotePort" /> is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
        public override void ActivateOptions()
        {
            base.ActivateOptions();

            if (_time_before_next_attempt < _minTimeBeforeNextAttemptInSec)
            {
                throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)_time_before_next_attempt,
                    "The value specified is less than " + _minTimeBeforeNextAttemptInSec.ToString() + " sec");
            }

            if (this.RemoteAddress == null)
            {
                throw new ArgumentNullException("The required property 'Address' was not specified.");
            }
            else if (this.RemotePort < IPEndPoint.MinPort || this.RemotePort > IPEndPoint.MaxPort)
            {
                throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("this.RemotePort", (object)this.RemotePort,
                    "The RemotePort is less than " +
                    IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) +
                    " or greater than " +
                    IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
            }
            else
            {
                this.RemoteEndPoint = new IPEndPoint(this.RemoteAddress, this.RemotePort);
                this.InitializeClientConnection();
            }
        }

        protected override void Append(LoggingEvent loggingEvent)
        {
            if (_nextTime.HasValue)
            {
                if (_nextTime > DateTime.Now)
                {
                    return;
                }

                _nextTime = null;
            }

            try
            {
                if (this.Client == null)
                {
                    this.Client = new TcpClient();
                    this.Client.Connect(this.RemoteEndPoint);
                }
                else
                    if (!this.Client.Connected)
                    {
                        this.Client.Close();
                        this.Client = new TcpClient();
                        this.Client.Connect(this.RemoteEndPoint);
                    }

                string data = RenderLoggingEvent(loggingEvent);
                Byte[] buffer = m_encoding.GetBytes(data.ToCharArray());

                this.Client.Client.Send(buffer);

                WriteLocalLog(data);


                //using (NetworkStream clientStream = this.Client.GetStream())
                //{
                //    clientStream.Write(buffer, 0, buffer.Length);
                //    clientStream.Flush();
                //    LogLog.Error("Write to stream");
                //}
            }
            catch (Exception ex)
            {
                _nextTime = DateTime.Now.AddSeconds(_time_before_next_attempt);

                LogLog.Error(this.GetType(),
                        string.Format("Unable to send logging event to remote host {0}  on port {1}", 
                                this.RemoteAddress.ToString(),
                                this.RemotePort), 
                        ex);

                //ErrorHandler.Error(
                //    "Unable to send logging event to remote host " +
                //    this.RemoteAddress.ToString() +
                //    " on port " +
                //    this.RemotePort + ".",
                //    ex,
                //    ErrorCode.WriteFailure);
            }
        }

        override protected bool RequiresLayout
        {
            get { return true; }
        }

        override protected void OnClose()
        {
            WriteLocalLog("OnClose()");
            base.OnClose();
            WriteLocalLog("OnClose() successfull");

            if (this.Client != null)
            {
                WriteLocalLog("Client.Close()");
                this.Client.Close();
                WriteLocalLog("Client.Close() successfull");
                this.Client = null;
            }
        }

        private void WriteLocalLog(string s)
        {
            //LogLog.Error(s);
        }

        protected virtual void InitializeClientConnection()
        {
            try
            {
                if (this.RemotePort != 0)
                {
                    this.Client = new TcpClient();//this.RemoteEndPoint);
                }
            }
            catch (Exception ex)
            {
                ErrorHandler.Error(
                    "Could not initialize the TcpClient connection on port " +
                    this.RemotePort.ToString(NumberFormatInfo.InvariantInfo) + ".",
                    ex,
                    ErrorCode.GenericFailure);

                this.Client = null;
            }
        }

        #region Private Instance Fields

        /// <summary>
        /// The IP address of the remote host or multicast group to which
        /// the logging event will be sent.
        /// </summary>
        private IPAddress m_remoteAddress;

        /// <summary>
        /// The TCP port number of the remote host or multicast group to
        /// which the logging event will be sent.
        /// </summary>
        private int m_remotePort;

        /// <summary>
        /// The cached remote endpoint to which the logging events will be sent.
        /// </summary>
        private IPEndPoint m_remoteEndPoint;

        /// <summary>
        /// The <see cref="UdpClient" /> instance that will be used for sending the
        /// logging events.
        /// </summary>
        private TcpClient m_client;

        /// <summary>
        /// The encoding to use for the packet.
        /// </summary>
        private Encoding m_encoding = Encoding.Default;

        /// <summary>
        /// Time before next attempt
        /// </summary>
        private int _time_before_next_attempt = _minTimeBeforeNextAttemptInSec;

        #endregion Private Instance Fields
    }

}

/*
 <appender name="TcpAppender" type="log4net.Appender.TCPAppender">
       <remoteAddress value="224.0.0.1" />
       <remotePort value="8080" />
       <timeBeforeNextAttempt value="10"/>
       <layout type="log4net.Layout.PatternLayout, log4net">
             <conversionPattern value="%-5level %logger [%property{NDC}] - %message%newline" />
       </layout>
</appender>
 */